/*
* Implement syntax highlighting
*/

#include "../inc/config.h"
#include "../inc/lexer.h"
#include "../inc/html.h"
#include "../inc/operate_file.h"
#include "../inc/log.h"
#include "../inc/queue.h"
#include <bits/pthreadtypes.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

int failednum = 0;
Queue *q_file;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

/* check the parameters wether is valid */
int check_parameters(char *parm, int order){
    int num;
    switch (order) {
    case 1:
        //check file path
        if (access(parm, F_OK) == -1) {
            fprintf(stderr, "%s does not exist.\n", parm);
            return 1;
        }
        break;
    case 2:
        // check debug type
        if(strcmp(parm, "none")== 0){
            set_debug(NONE);
        }else if(strcmp(parm, "info")== 0){
            set_debug(INFO);
        }else if(strcmp(parm, "debug")== 0){
            set_debug(DEBUG);
        }else if(strcmp(parm, "fatal") == 0){
            set_debug(FATAL);
        }else{
            fprintf(stderr, "-d option: %s invalid argument.\n", parm);
            return 1;
        }
        break;
    case 3:
        // check thread amount
        num = atoi(parm);
        if(num > 0 && num <= 4){
            set_thread_num(num);
        }else{
            fprintf(stderr, "-t option: %s invalid argument.\n", parm);
            return 1; 
        }
        break;
    }
    
    return 0;
}

/* initialize the hash tabale */
void init_pool(Link **link, const char *token[], int size, enum symbol_type type){
    for(int i = 0; i < size; ++i){
      insert(link, token[i], type);
    }
}

/* initialize the token dictionary */
void init_token_map(Link **link){
    init_pool(link, keywords, get_keywords_size(), KEYWORDS);
    init_pool(link, preprocess, get_preprocess_size(), PREPROCESS);
    init_pool(link, single_operator, get_single_operator_size(), OPERATOR);
    init_pool(link, delimiter, get_delimiter_size(), DELIMITER);
}

/* initialize the token */
Token* init_token(){
	Token *t = (Token*)malloc(sizeof(Token));
	t->pos_begin = 0;
	t->pos_end = 0;
	t->type = UNRECOGNIZED;
    t->next_type = -1;
	return t;
}

/* perform syntax analysis on each file */
int lex(Link **link, const char *content, long length, char *file, FILE *html, int printline) {
    int ret = 0;
    int pos = 0;
    int line_num = 1;
    Queue *qf = queue_create();
    init_lex(qf, link, content, length, file);
    Token *token = init_token();
    html_head(html, file);
    if(printline){
        fprintf(html, "<p11>%d&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p11>", line_num);
    }
    do{
        char convert_str[40];
        get_symbol(token, pos, line_num, 1);
        // printf("%s \t %s\n", token->token, get_symbol_type(token->type));
        if(ret == 0 && UNRECOGNIZED == token->type) ret = 1;
        while(pos < token->pos_begin){
            if(content[pos] == '\n' && printline){
                fprintf(html, "<br><p11>%d&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p11>", ++line_num);
            }else{
                if(ascii_2_html(convert_str, content[pos])){
                    fputs(convert_str, html);
                }
                else fputc(content[pos], html);
            }
            pos++;
        }
        fputs("<", html);
        fputs(get_symbol_html_color(token->type, token->next_type), html); 
        fputs(">", html);
        for(; pos <= token->pos_end; pos++){
            if(content[pos] == '\n'  && printline){
                fprintf(html, "<br><p11>%d&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p11>", ++line_num);
            }else{
                if(ascii_2_html(convert_str, content[pos])){
                    fputs(convert_str, html);
                }else{ 
                    fputc(content[pos], html);
                }
            }
        }
        fputs("</", html);
        fputs(get_symbol_html_color(token->type, token->next_type), html); 
        fputs(">", html);
        if(pos == length) fputs("<br>", html);
    }while(pos < length);
    clear_lex();
    free(token);
    html_tail(html);
    if(!is_empty(qf)){
        Pair *pair = (Pair*)malloc(sizeof(Pair));
        int* flag = (int*)malloc(sizeof(int));
        *flag = 0;
        pair->first = flag;
        pair->second = qf;
        file_queue_push(pair);
    }else{
        queue_destroy(qf);
    }
    return ret;
}

/* perform syntax highlight on each file */
int highlight(const char *content, long length, char *file, FILE *html) {
  Link **link = init_link(MAP_SIZE);
  init_token_map(link);
  int ret = lex(link, content, length, file, html, 1);
  destroy_link(link, MAP_SIZE);
  return ret;
}

/* outpput to a html file after syntax highlight */
int output_html(char *filename, char* savefile){
  FILE *src_file = fopen(filename, "r");
  if(src_file == NULL){
    _log(NONE, __FILE__, "%s does not exist.", filename);
    return -1;
  }
  // SEEK_END end of file
  // SEEK_SET beginning of file
  // SEEK_CUR current position of the file pointer
  fseek(src_file, 0, SEEK_END);
  // The offset of the file pointer relative to the beginning
  long length = ftell(src_file);
  // rewind() same to fseek(fp, 0, SEEK_SET);
  rewind(src_file);
  // 1 is resrve space for '\0' at end of file
  char *content = (char*)malloc(length * sizeof(char) + 1);
  if (content == NULL){
    _log(NONE, __FILE__, "there is not enough memory space to load the %s", filename);
    fclose(src_file);
    return -1;
  }
  size_t size = fread(content, 1, length, src_file);
  if(size != length){ 
    _log(NONE, __FILE__, "fail to read %s", filename);
    fclose(src_file);
    free(content);
    return -1;
  }
  content[length] = '\0';
  
  FILE *html_file = fopen(savefile, "w");
  if (html_file == NULL) {
    _log(NONE, __FILE__, "error opening %s", savefile);
    return -1;
  }
  char file_nopath[300];
  int ret;
  if(get_filename_without_path(filename, file_nopath)){
    ret = highlight(content, length, file_nopath, html_file);
  }else{
    ret = highlight(content, length, filename, html_file);
    }
  fclose(src_file);
  free(content);
  fclose(html_file);
  // ---------------------------
  // only output failed file
  if(get_debug() == FATAL && ret==0) remove(savefile);
  // ---------------------------
  return ret;
}

/* syntax analysis thread */
void* output_thread(void* arg) {
    int id = *(int*)arg;
    while (1) {
        pthread_mutex_lock(&mutex);
        if(queue_size(q_file) == 0){
            pthread_mutex_unlock(&mutex);
            break; 
        }
        Pair *p = (Pair*)queue_pop(q_file);
        printf("thread %d src: %s\n", id, (char*)p->first);
        if(output_html(p->first, p->second)) failednum++;
        free(p->first);
        free(p->second);
        free(p);
        pthread_mutex_unlock(&mutex);
    }
    printf("file pthread exit...\n");
    return NULL;
}

int main(int argc, char *argv[]) {
  int ch;
  opterr = 0; 
  char *filepath = NULL; 
  char *oval = NULL;  
  char *dval = NULL; 
  char *tval = NULL; 
  const char *help = "Usage: syntax_highlighter [options] file...\noptions:\n-h            display usage information\n-o            output file path\n-d            display the debug level, value = {none|info|debug|fatal(only output failed files)}\n-t            set the number of threads, value = (1 - 8)";

  while ((ch = getopt(argc, argv, "o:d:t:h")) != -1) {  
      switch (ch) {  
      case 'o':  
          oval = optarg; 
          if(check_parameters(oval, 1)) return 1;
          break;  
      case 'd':  
          dval = optarg; 
          if(check_parameters(dval, 2)) return 1;
          break;  
      case 't':  
          tval = optarg; 
          if(check_parameters(tval, 3)) return 1;
          break;  
      case 'h':
          printf("%s\n", help);
          return 1;
      case '?': 
          if(optopt == 'o' || optopt == 'd' || optopt == 't'){
              fprintf(stderr, "option: -%c requires an parameter.\n", optopt);
          }else{
              fprintf(stderr, "invalid option: '%c', please use 'h' option to get help information.\n", optopt);
          }
          return 1; 
      }  
  }
  if(argc == 1){
      printf("%s\n", help);
      return 1;
  }else if(argc > 8){
      fputs("too many arguments.\n", stderr);
      return 1;
  }else if(optind + 1 != argc){
      fputs("please input filename.\n", stderr);
      return 1;
  }else if(oval == NULL){
      fprintf(stderr, "please input the output path.\n");
    return 1;
  }
  //////////////////////////////////////////////////////////////////////
  if(init_log()) return 1;
  struct timeval start, end;
  gettimeofday(&start, NULL);
  filepath = argv[argc-1];
  int filenum = 0;
  // it is a file
  if(is_dir(filepath) == 2){
    if(is_cfile(filepath) && !is_file_empty(filepath)){
        filenum++;
        char save[300], nopath[200];
        if(get_filename_without_path(filepath, nopath)) snprintf(save, sizeof(save), "%s/%s.html", oval, nopath);
        else snprintf(save, sizeof(save), "%s/%s.html", oval, filepath);
        if(output_html(filepath, save)) failednum++;
        printf("total files: %d   failed files: %d\n", filenum, failednum);
        _log(NONE, __FILE__, "total files: %d   failed files: %d", filenum, failednum);
    }else{
        fprintf(stderr, "%s is not a c file.\n", filepath);
    }
  }
  // it is a dir
  else if(is_dir(filepath) == 1){
    q_file = queue_create();
    list_files(filepath, oval, q_file);
    filenum = queue_size(q_file);
    if (filenum == 0){
        queue_destroy(q_file);
        gettimeofday(&end, NULL);
        cost_time(&start, &end);
        return 1;
    }
    ////////////////////////////////////////////////////////////////// 
    int thread_num;
    if(filenum <= 20) thread_num = 1;
    else if(filenum > 200) thread_num = 4;
    else{
        if(get_thread_num() > 1 && get_thread_num() < 4) thread_num = get_thread_num();
        else thread_num = 2;
    }
    pthread_t threads[thread_num];
    for(int i = 0; i < thread_num; i++){
        pthread_create(&threads[i], NULL, output_thread, &i);
        // usleep(1000);  //sleep 1ms
    }

    for(int i = 0; i < thread_num; i++){
        int r = pthread_join(threads[i], NULL);
        if(r!=0) printf("pthread %d join error.\n", i);
    }
    
    pthread_mutex_destroy(&mutex);
    queue_destroy(q_file);
    printf("total files: %d   failed files: %d\n", filenum, failednum);
    _log(NONE, __FILE__, "total files: %d   failed files: %d", filenum, failednum);
  }else{
    fprintf(stderr,"%s does not exist.\n", filepath);
  }

  gettimeofday(&end, NULL);
  cost_time(&start, &end);
  // only output failed file
  if(get_debug() == FATAL) delete_empty_folder(oval);
  return 0;
}
